import type {EditorFromTextArea} from 'codemirror';
import {CODE_MIRROR_OPTIONS, MAX_VERTEX_COUNT, MIN_VERTEX_COUNT} from '../common';
import {GeometryEditor, GeometryEditorGeometry, MMapsReactifyContext} from '../react-geometry-editor';
import {INITIAL_GEOMETRY, LOCATION, BEHAVIORS} from '../variables';

window.map = null;
const {useState, useEffect, useCallback, useMemo, useRef} = React;

main();
async function main() {
    // For each object in the JS API, there is a React counterpart
    // To use the React version of the API, include the module @mappable-world/mappable-reactify
    const [mappableReact] = await Promise.all([mappable.import('@mappable-world/mappable-reactify'), mappable.ready]);
    const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
    const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapControls, MMapControl, MMapControlButton} =
        reactify.module(mappable);

    const CodeArea = (props: {geometry: GeometryEditorGeometry; onChangeValue: (value: string) => void}) => {
        const codeMirrorRef = useRef<HTMLTextAreaElement>(null);
        const editorRef = useRef<EditorFromTextArea>(null);

        const applyGeoJson = useCallback(() => {
            const newValue = editorRef.current.getValue();
            props.onChangeValue(newValue);
        }, [props.onChangeValue]);

        useEffect(() => {
            editorRef.current = CodeMirror.fromTextArea(codeMirrorRef.current, CODE_MIRROR_OPTIONS);
            editorRef.current.setSize('100%', '100%');
            return () => editorRef.current.toTextArea();
        }, []);

        useEffect(() => {
            editorRef.current.setValue(JSON.stringify(props.geometry, null, 2));
        }, [props.geometry]);

        return (
            <div className="geojson-editor">
                <textarea ref={codeMirrorRef} />
                <button onClick={applyGeoJson}>Apply GeoJSON</button>
            </div>
        );
    };

    function App() {
        const [geometry, setGeometry] = useState<GeometryEditorGeometry>(INITIAL_GEOMETRY);
        const [editMode, setEditMode] = useState(true);

        const area = useMemo(() => {
            return geometry.type === 'Polygon'
                ? `${(turf.area(geometry) / 1_000_000).toFixed(2)} km²`
                : 'Not valid geometry';
        }, [geometry]);

        const onChangeGeometry = useCallback((geometry) => {
            setGeometry(geometry);
        }, []);

        const toggleEditMode = useCallback((e) => {
            setEditMode(e.target.checked);
        }, []);

        const onEditorChangeValue = useCallback((value: string) => {
            try {
                const rawGeometryObject = JSON.parse(value);
                let feature;
                if (rawGeometryObject.type === 'Polygon') {
                    feature = turf.polygon(rawGeometryObject.coordinates);
                    const vertexCount = turf.getCoords(feature)[0].length - 1;
                    if (vertexCount < MIN_VERTEX_COUNT || vertexCount > MAX_VERTEX_COUNT) {
                        throw Error(`Polygon must have between ${MIN_VERTEX_COUNT} and ${MAX_VERTEX_COUNT} vertices`);
                    }
                } else if (rawGeometryObject.type === 'LineString') {
                    feature = turf.lineString(rawGeometryObject.coordinates);
                    const vertexCount = turf.getCoords(feature).length;
                    if (vertexCount < MIN_VERTEX_COUNT || vertexCount > MAX_VERTEX_COUNT) {
                        throw Error(
                            `LineString must have between ${MIN_VERTEX_COUNT} and ${MAX_VERTEX_COUNT} vertices`
                        );
                    }
                } else {
                    throw Error('Not valid geometry. Support only Polygon and LineString');
                }

                setGeometry(feature.geometry as GeometryEditorGeometry);
            } catch (error) {
                alert(error.message);
            }
        }, []);

        return (
            // Initialize the map and pass initialization parameters
            <div className="container">
                <div className="map">
                    <MMapsReactifyContext.Provider value={reactify}>
                        <MMap
                            behaviors={BEHAVIORS}
                            location={reactify.useDefault(LOCATION)}
                            showScaleInCopyrights={true}
                            ref={(x) => (map = x)}
                        >
                            {/* Add a map scheme layer */}
                            <MMapDefaultFeaturesLayer />
                            <MMapDefaultSchemeLayer />

                            <MMapControls position="top">
                                <MMapControl transparent>
                                    <div className="area">Area: {area}</div>
                                </MMapControl>
                            </MMapControls>

                            <MMapControls position="top left">
                                <MMapControl transparent>
                                    <div className="edit-mode-control">
                                        <input
                                            id="edit-mode"
                                            type="checkbox"
                                            onChange={toggleEditMode}
                                            defaultChecked={editMode}
                                        />
                                        <label className="toggle-control" htmlFor="edit-mode"></label>
                                        <span className="text-label">Edit Mode</span>
                                    </div>
                                </MMapControl>
                            </MMapControls>

                            <GeometryEditor
                                editMode={editMode}
                                geometry={geometry}
                                onChange={onChangeGeometry}
                                maxVertex={MAX_VERTEX_COUNT}
                                minVertex={MIN_VERTEX_COUNT}
                            />
                        </MMap>
                    </MMapsReactifyContext.Provider>
                </div>
                <CodeArea geometry={geometry} onChangeValue={onEditorChangeValue} />
            </div>
        );
    }

    ReactDOM.render(
        <React.StrictMode>
            <App />
        </React.StrictMode>,
        document.getElementById('app')
    );
}
